package com.atguigu.springcloud.cloudsentinelgatewaydevelop.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.atguigu.springcloud.cloudsentinelgatewaydevelop.bean.DO.RouteDefinitionDO;
import com.atguigu.springcloud.cloudsentinelgatewaydevelop.bean.dto.RouteDefinitionDTO;
import com.atguigu.springcloud.cloudsentinelgatewaydevelop.mapper.RouteDefinitionMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.filter.FilterDefinition;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.handler.AsyncPredicate;
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionLocator;
import org.springframework.cloud.gateway.route.RouteDefinitionRepository;
import org.springframework.cloud.gateway.support.NotFoundException;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static java.util.Collections.synchronizedMap;

@Slf4j
@Service
public class RouteBeanDefinitionLocatorService implements RouteDefinitionLocator, InitializingBean, ApplicationEventPublisherAware {

    @Autowired
    RouteDefinitionMapper routeDefinitionMapper;

    ApplicationEventPublisher publisher;
    private final Map<String, RouteDefinition> routes = synchronizedMap(
            new LinkedHashMap<String, RouteDefinition>());


    public void save(Mono<RouteDefinition> route) {
        route.subscribe(r -> routes.put(r.getId(),r));
    }


    public void delete(Mono<String> routeId) {
         routeId.subscribe(id -> {
            if (routes.containsKey(id)) {
                routes.remove(id);
            }
        });
    }

    public Mono<Boolean> saveRouteDefinition(RouteDefinitionDTO routeDefinitionDtoToSave){

        RouteDefinitionDO routeDefinitionDO = convertRoteDefinitionDTO2RoteDefinitionDO(routeDefinitionDtoToSave);
        routeDefinitionMapper.save(routeDefinitionDO).log().subscribe(ret->log.info("插入："+JSON.toJSONString(ret)));
        save(Mono.just(routeDefinitionDtoToSave).map(
                routeDefinitionDto -> {
                    RouteDefinition routeDefinition=new RouteDefinition();
                    try {
                        routeDefinition.setUri(new URI(routeDefinitionDto.getUri()));
                    } catch (URISyntaxException e) {
                        e.printStackTrace();
                    }
                    routeDefinition.setId(routeDefinitionDto.getRouteId());
                    routeDefinition.setOrder(routeDefinitionDto.getRouteOrder());
                    routeDefinition.setMetadata(routeDefinitionDto.getMetadata());
                    List<PredicateDefinition> predicatesCollect = routeDefinitionDto.getPredicateDefinitions().stream().map(predicateJsonStr -> {
                        return new PredicateDefinition((String) predicateJsonStr);
                    }).collect(Collectors.toList());
                    List<FilterDefinition> filtersCollect = routeDefinitionDto.getFilterDefinitions().stream().map(filterJsonStr -> {
                        return new FilterDefinition((String) filterJsonStr);
                    }).collect(Collectors.toList());
                    routeDefinition.setPredicates(predicatesCollect);
                    routeDefinition.setFilters(filtersCollect);
                    return routeDefinition;
                }
        ));
        publisher.publishEvent(new RefreshRoutesEvent(this));
        return Mono.just(true);
    }
    public Mono<Boolean> deleteRoute(RouteDefinitionDTO routeDefinitionDTO){
        RouteDefinitionDO routeDefinitionDOToDelete=new RouteDefinitionDO();
        delete(Mono.just(routeDefinitionDTO.getRouteId()));
        routeDefinitionDOToDelete.setRouteId(routeDefinitionDTO.getRouteId());
        routeDefinitionDOToDelete.setId(routeDefinitionDTO.getId());
        routeDefinitionMapper.delete(routeDefinitionDOToDelete).log().subscribe(item->log.info("执行删除:id="+routeDefinitionDTO.getId()+" routeId="+routeDefinitionDTO.getRouteId()));
        publisher.publishEvent(new RefreshRoutesEvent(this));
        return Mono.just(true);
    }


    @Override
    public Flux<RouteDefinition> getRouteDefinitions() {
        return Flux.fromIterable(routes.values());
    }


    @Override
    public void afterPropertiesSet() throws Exception {
        List<RouteDefinition> list=new ArrayList<>();
        routeDefinitionMapper.findAll()
                .map(routeDefinitionDO -> {
                    RouteDefinition routeDefinition = new RouteDefinition();
                    try {
                        routeDefinition.setUri(new URI(routeDefinitionDO.getUri()));
                    } catch (URISyntaxException e) {
                        e.printStackTrace();
                    }
                    routeDefinition.setId(routeDefinitionDO.getRouteId());
                    routeDefinition.setOrder(routeDefinitionDO.getRouteOrder());
                    routeDefinition.setMetadata(JSON.parseObject(routeDefinitionDO.getMetadata()));
                    JSONArray predicates = JSON.parseArray(routeDefinitionDO.getPredicateDefinitions());
                    JSONArray filters = JSON.parseArray(routeDefinitionDO.getFilterDefinitions());
                    List<PredicateDefinition> predicatesCollect = predicates.stream().map(predicateJsonStr -> {
                        return new PredicateDefinition((String) predicateJsonStr);
                    }).collect(Collectors.toList());
                    List<FilterDefinition> filtersCollect = filters.stream().map(filterJsonStr -> {
                        return new FilterDefinition((String) filterJsonStr);
                    }).collect(Collectors.toList());
                    routeDefinition.setFilters(filtersCollect);
                    routeDefinition.setPredicates(predicatesCollect);

                    return routeDefinition;
                }).subscribe(routeDefinition->routes.put(routeDefinition.getId(),routeDefinition));


    }

    public RouteDefinitionDO convertRoteDefinitionDTO2RoteDefinitionDO(RouteDefinitionDTO routeDefinitionDTO ){
        RouteDefinitionDO routeDefinitionDO =new RouteDefinitionDO();
        routeDefinitionDO.setId(routeDefinitionDTO.getId());
        routeDefinitionDO.setRouteId(routeDefinitionDTO.getRouteId());
        routeDefinitionDO.setRouteOrder(routeDefinitionDTO.getRouteOrder());
        routeDefinitionDO.setUri(routeDefinitionDTO.getUri());
        routeDefinitionDO.setFilterDefinitions(JSON.toJSONString(routeDefinitionDTO.getFilterDefinitions()));
        routeDefinitionDO.setPredicateDefinitions(JSON.toJSONString(routeDefinitionDTO.getPredicateDefinitions()));
        routeDefinitionDO.setMetadata(JSON.toJSONString(routeDefinitionDTO.getMetadata()));
        return routeDefinitionDO;
    }

    public RouteDefinitionDTO convertRoteDefinitionDO2RoteDefinitionDTO(RouteDefinitionDO routeDefinitionDO){
        RouteDefinitionDTO routeDefinitionDTO=new RouteDefinitionDTO();
        routeDefinitionDTO.setId(routeDefinitionDO.getId());
        routeDefinitionDTO.setRouteId(routeDefinitionDO.getRouteId());
        routeDefinitionDTO.setUri(routeDefinitionDO.getUri());
        routeDefinitionDTO.setRouteOrder(routeDefinitionDO.getRouteOrder());
        routeDefinitionDTO.setPredicateDefinitions(JSON.parseArray(routeDefinitionDO.getPredicateDefinitions(),String.class));
        routeDefinitionDTO.setFilterDefinitions(JSON.parseArray(routeDefinitionDO.getFilterDefinitions(),String.class));
        return routeDefinitionDTO;
    }

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        publisher=applicationEventPublisher;
    }
    public Mono<List<String>> getAllRouteInMem(){
        return Mono.fromCallable(()->routes.keySet().stream().collect(Collectors.toList()));
    }

    public Mono<List<RouteDefinitionDTO>> getAllRouteDefinition(int page,int size) {
        int offset=page*size;
        Mono<List<RouteDefinitionDTO>> routeDefinitionDTOs = routeDefinitionMapper.getPageList(offset,size).map(item -> convertRoteDefinitionDO2RoteDefinitionDTO(item)).collectList();
        return routeDefinitionDTOs;
    }

    public Mono<RouteDefinitionDTO> getRouteByRouteID(String routeId){
        return routeDefinitionMapper.getByRouteId(routeId)
                .map(this::convertRoteDefinitionDO2RoteDefinitionDTO);
    }
}
